package org.ogv.usada.states;

import java.util.ArrayList;

import org.ogv.OGV;
import org.ogv.Spawner;
import org.ogv.collision.BoundingSphere;
import org.ogv.usada.models.Content;
import org.ogv.usada.models.Customer;
import org.ogv.usada.models.Customer.CustomerListener;
import org.ogv.usada.models.GP;
import org.ogv.usada.models.Leaf;
import org.ogv.usada.models.LevelDef;
import org.ogv.usada.models.Pot;
import org.ogv.usada.models.Recipe;
import org.ogv.usada.models.SlicedLeaf;
import org.ogv.usada.views.GameHUD;
import org.ogv.usada.views.HowTo;
import org.ogv.usada.views.Option;
import org.ogv.usada.views.Slice;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.ParticleEffect;
import com.badlogic.gdx.graphics.g2d.ParticleEffectPool;
import com.badlogic.gdx.graphics.g2d.ParticleEffectPool.PooledEffect;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GameScreen extends ScreenState implements CustomerListener {

	//Defs
	private LevelDef 	_levDef;
	private int			_lvIdx;
	
	//Rendering vars
	private Sprite				_bg;
	private Sprite				_bg0;
	private GameHUD				_hud;
	private Slice				_slice;
	private Option				_option;
	private HowTo				_recipeBook;
	
	//Sound
	private Sound	_kaching;
	private Sound	_recipeFail;
	private Music	_bgm;
	
	//particles
	private ParticleEffectPool 		_peSplashPool;
	private ArrayList<PooledEffect> _peSplashes;
	private ParticleEffectPool 		_peSplatPool;
	private ArrayList<PooledEffect> _peSplats;
	
	//Entities
	private Spawner<Byte>		_custSpawner;
	private Spawner<Byte>		_leafSpawner;
	private Spawner<Byte>		_illnessSpawner;
	private Spawner<Integer>	_leafCountSpawner;
	private ArrayList<Leaf> 	_leaves;
	private ArrayList<SlicedLeaf> 	_slicedLeaves;
	private ArrayList<Object>	_temps;
	private Customer[]			_seats;
	private Customer			_activeCustomer;
	private Pot					_pot;
	
	//score and time
	private int					_score;
	private float				_time;
	private int					_state;

	private static final int STATE_NORMAL 	= 0;
	private static final int STATE_RECIPE 	= 1;
	private static final int STATE_PAUSE 	= 2;
	private static final int STATE_DONE 	= 3;
	
	public GameScreen(SpriteBatch sb) {
		super(sb);
		_id = GP.$().STATE_GAME;
		
		////Initialize members		
		//entities arr
		_leafSpawner 	= new Spawner<Byte>();
		_custSpawner	= new Spawner<Byte>();
		_illnessSpawner	= new Spawner<Byte>();
		_leafCountSpawner = new Spawner<Integer>();
		_leaves 		= new ArrayList<Leaf>();
		_slicedLeaves	= new ArrayList<SlicedLeaf>();
		_temps			= new ArrayList<Object>();
		_seats			= new Customer[] { null, null, null, null, null };
		_pot			= new Pot();
		
		_hud = new GameHUD();
	}

	@Override
	public void init() {	
		//Restart content, so it resolves resources dependancies
		Content.restart();
		
		////particle
		//splash
		ParticleEffect pe = new ParticleEffect();
		pe.load(Gdx.files.internal("data/particle/splash.particle"), Gdx.files.internal("data/particle"));
		_peSplashPool 	= new ParticleEffectPool(pe, 4, 15);
		_peSplashes		= new ArrayList<ParticleEffectPool.PooledEffect>();
		//splat
		pe = new ParticleEffect();
		pe.load(Gdx.files.internal("data/particle/splat.particle"), Gdx.files.internal("data/particle"));
		_peSplatPool 	= new ParticleEffectPool(pe, 4, 15);
		_peSplats 		= new ArrayList<ParticleEffectPool.PooledEffect>();
		
		//fetch LevelDef from StateManager's exchange buffer
		_lvIdx 		= (Integer) getStateMgr().popValueFromExchangeBuff(); 
		_levDef 	= Content.$().levels[_lvIdx];
		_time		= _levDef.time;
		_score		= 0;
		
		//spritebatch to camera
		_sb.setProjectionMatrix(_cam.combined);
		Gdx.gl.glClearColor(GP.$().GAME_BGCOLOR.r, GP.$().GAME_BGCOLOR.g, GP.$().GAME_BGCOLOR.b, GP.$().GAME_BGCOLOR.a);
		
		//entities
		_leafSpawner.init(_levDef.leafTypes, _levDef.leafSpawnTime[0], _levDef.leafSpawnTime[1]);
		_custSpawner.init(_levDef.customerTypes, _levDef.custSpawnTime[0], _levDef.custSpawnTime[1]);
		_illnessSpawner.init(_levDef.illnessTypes, 0, 0);
		_leafCountSpawner.init(_levDef.leafSpawnCount, 0, 0);
		_pot.init(Content.$().pots[0], GP.$().GAME_WIDTH >> 1, (GP.$().GAME_HEIGHT >> 1) - 200);
		
		//background
		_bg 	= new Sprite(OGV.asset.getTextureRegion(_levDef.bgFile[0]));
		_bg0 	= new Sprite(OGV.asset.getTextureRegion(_levDef.bgFile[1]));
		_option = new Option(true);
		_recipeBook	= new HowTo(_levDef.recipeBook);
		_slice 	= new Slice();
		
		//hud
		_hud.init();
		
		//play music
		_bgm = OGV.asset.get(_levDef.music, Music.class);
		_bgm.play();
		_bgm.setVolume(GP.$().MUSIC_VOL);
		_bgm.setLooping(true);
		
		//sound
		_recipeFail	= OGV.asset.get(GP.$().RES_SOUND_RECIPEFAIL, Sound.class);
		_kaching 	= OGV.asset.get(GP.$().RES_SOUND_KACHING, Sound.class);		
		
		_state = STATE_NORMAL;
		
		//Initialize doors
		initializeDoors();
		startOpeningDoor();
	}
	
	@Override
	public void resume() {
		//Check for restart
		if(getStateMgr().hasValueInExchangeBuff())
			init();
	}
	
	@Override
	public void pause() {
		destroy();
	}
	
	@Override
	public void destroy() {
		//Clear entities
		for (Leaf l : _leaves) l.free();
		_leaves.clear();
		for (SlicedLeaf l : _slicedLeaves) l.free();
		_slicedLeaves.clear();
		for (int i = 0; i < _seats.length; ++i) {
			if(_seats[i] != null) _seats[i].free();
			_seats[i] = null;
		}
		for(PooledEffect pe : _peSplashes) pe.free();
		_peSplashes.clear();
		for(PooledEffect pe : _peSplats) pe.free();
		_peSplats.clear();
		_activeCustomer = null;
		_option 		= null;
		_recipeBook		= null;
		_slice			= null;
		
		//Clear pools
		Leaf.clearPool();
		BoundingSphere.clearPool();
		Customer.clearPool();
		_peSplashPool.clear();
		_peSplatPool.clear();
		
		//stop music
		OGV.asset.get(_levDef.music, Music.class).stop();
	}
	
	@Override
	public void run(float elapsedTime) {
		//Update doors
		if(areDoorsActive()) {
			updateDoors(elapsedTime);
			render();
			return;
		}
		
		//Pause the game if it is paused
		else if(_state == STATE_PAUSE) {
			updateTouchPoint();
			_option.update(elapsedTime, Gdx.input.isTouched(), _tempV3.x, _tempV3.y, _bgm);
			if(_option.lastTouchWasResume())
				_state = STATE_NORMAL;
			else if(_option.lastTouchWasExit())
				requestStateChange(-1);
			render();
			return;
		}
		
		//Update recipe book if it is visible
		else if(_state == STATE_RECIPE) {
			updateTouchPoint();
			_recipeBook.update(elapsedTime, Gdx.input.justTouched(), _tempV3.x, _tempV3.y);
			if(_recipeBook.backTouched())
				_state = STATE_NORMAL;
			render();
			return;
		} 
		
		//Game is already done ..
		else if(_state == STATE_DONE) {
			render();
			return;
		}
		
		//Time
		_time -= elapsedTime;
		if(_time <= 0) {
			gameOver();
		}
		
		//Handle input
		boolean touch = false;
		if(touch = Gdx.input.justTouched()) {
			updateTouchPoint();
		}

		
		//pause
		if(touch && _hud.isMenuClicked(_tempV3.x, _tempV3.y)) {
			_state = STATE_PAUSE;
			return;
		}
		
		//recipe book requested
		if(touch && _hud.isRecipeClicked(_tempV3.x, _tempV3.y)) {
			_state = STATE_RECIPE;
		}
		
		//spawns
		if(_leafSpawner.update(elapsedTime)) spawnLeaves();
		if(_custSpawner.update(elapsedTime)) spawnCustomer();
		
		////update and destroy entities
		//leaf
		for (int i = _leaves.size() - 1; i >= 0; --i) {
			Leaf l = _leaves.get(i);
			
			//update it
			l.update(elapsedTime);
			
			//kill it if out of display
			if(isOutOfDisplay(l.leafSprite)) {
				_temps.add(l);
			} 
			
			//kill and slice it when touched
			else if(touch && _pot.isPointOnTopFromMe(_tempV3.y) && l.bSphere.isTouch(_tempV3.x, _tempV3.y)) {
				_temps.add(l);
				sliceLeaf(l);
				
				//override touch, no more touch processing
				touch = false;
			}
		}
		//destroy killed leaves
		for(Object l : _temps) { _leaves.remove(l); ((Leaf)l).free(); }
		_temps.clear();
		
		//customer
		for (int i = 0; i < _seats.length; ++i) {
			if(_seats[i] == null) continue;
			_seats[i].update(elapsedTime);
			
			//kill
			if(_seats[i].hasDone()) {
				freeCustomer(i);
			} 
			
			//process touch
			else if(_seats[i].isWaitingForMed() && _seats[i].isTouched(_tempV3.x, _tempV3.y - GP.$().TOUCH_OFFSET_Y)) {
				if(_activeCustomer != null) _activeCustomer.showRecipe(false);
				_activeCustomer = _seats[i];
				_activeCustomer.showRecipe(true);
			}
		}
		
		//spliced leaves
		for (SlicedLeaf l : _slicedLeaves) {
			l.update(elapsedTime);
			if(l.hasDone()) {
				_temps.add(l);
				
				//add splash to pot
				_pot.makeSplash(l.getSprite().getX(), l.getSprite().getY());
			}
		}
		for(Object l : _temps) { _slicedLeaves.remove(l); ((SlicedLeaf)l).free(); }
		_temps.clear();
		
		//pot
		_pot.update(elapsedTime);
		
		////particles
		//splash
		for(PooledEffect pe : _peSplashes) {
			pe.update(elapsedTime);
			if(pe.isComplete()) _temps.add(pe);
		}
		for(Object l : _temps) { _peSplashes.remove(l); ((PooledEffect)l).free(); }
		_temps.clear();
		
		//splats
		for(PooledEffect pe : _peSplats) {
			pe.update(elapsedTime);
			if(pe.isComplete()) _temps.add(pe);
		}
		for(Object l : _temps) { _peSplats.remove(l); ((PooledEffect)l).free(); }
		_temps.clear();
		
		_slice.update(elapsedTime);
		
		//render
		render();
	}

	private void gameOver() {
		Content.$().updateScore(_lvIdx, _score);
		getStateMgr().pushValueInExchangeBuff(new Integer(_score));
		getStateMgr().pushValueInExchangeBuff(new Integer(_lvIdx));
		requestStateChange(GP.$().STATE_GAMEOVER);
	}

	private void render() {
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		_sb.begin();
		
		_bg0.draw(_sb);
		
		//draw splats
		for(PooledEffect pe : _peSplats) pe.draw(_sb);
		
		//draw particles
		for(PooledEffect pe : _peSplashes) pe.draw(_sb);
		
		//draw pot
		_pot.draw(_sb);
		
		//draw leaves
		for (Leaf l : _leaves) l.draw(_sb);
		for (SlicedLeaf l : _slicedLeaves) l.draw(_sb);
		_slice.draw(_sb);
		
		//draw bg
		_bg.draw(_sb);
		
		//draw customers
		for(int i = _seats.length - 1; i >= 0; --i) {
			Customer c = _seats[i];
			if(c != null) c.draw(_sb);
		}
		
		//draw hud
		_hud.draw(_sb, _score, _time/_levDef.time);
		
		if(_state == STATE_PAUSE) 
			_option.draw(_sb);
		else if(_state == STATE_RECIPE) 
			_recipeBook.draw(_sb);
		
		//Draw doors
		drawDoors();
		
		_sb.end();
	}
	
	private void freeCustomer(int seatIdx) {
		//if that customer is currently active, remove that customer
		if(_activeCustomer == _seats[seatIdx]) {
			_activeCustomer = null;
			_pot.shakePot(1, true);
		}
		
		//actually free them
		_seats[seatIdx].free();
		_seats[seatIdx] = null;
	}
	
	private void spawnLeaves() {
		int numLeaf = _leafCountSpawner.getSpawn().intValue();
		for(int i = 0; i < numLeaf; ++i) {
			//add leaf into the game
			Leaf l = Leaf.obtain();
			_leaves.add(l);
			
			//Launching leaf
			float launchX = (GP.$().GAME_WIDTH >> 2) + GP.$().RANDOM.nextInt(GP.$().GAME_WIDTH >> 1);
			float launchY = 0;
			float targetX = (GP.$().GAME_WIDTH >> 2) + GP.$().RANDOM.nextInt(GP.$().GAME_WIDTH >> 1);
			float targetY = GP.$().GAME_HEIGHT;
			float vx 	= targetX - launchX;
			float vy 	= targetY - launchY;
			float vlen	= (float) Math.sqrt(vx*vx + vy*vy);
			vx /= vlen;
			vy /= vlen;
			float speed = GP.$().GAME_LEAF_SPEED;
			l.init(_leafSpawner.getSpawn(), launchX, launchY, vx * speed, vy * speed, GP.$().RANDOM.nextFloat() * 180 - 90);
		}
	}

	private void spawnCustomer() {
		//find a seat
		int seatIdx;
		for(seatIdx = 0; seatIdx < _seats.length; ++seatIdx) {
			if(_seats[seatIdx] == null) break;
		}
		if(seatIdx == _seats.length) return; //exit if didn't find a seat
		
		//spawn a customer
		_seats[seatIdx] = Customer.obtain().init(
			_levDef.customerSeatPos[seatIdx], 
			0f, 
			_custSpawner.getSpawn().byteValue(), 
			_illnessSpawner.getSpawn().byteValue(),
			this
		);
	}
	
	private void sliceLeaf(Leaf l) {
		//check whether the sliced leaf is correct
		checkLeaf(l);
		
		//insert new sliced leaves to be displayed
		l.getSlicedLeaves(_slicedLeaves, _pot);
		
		//play the sound
		l.slicedSound.play(GP.$().SOUND_VOL);
		
		_slice.spawn(l);
		
		////add particle effect
		//splash
		PooledEffect pe = _peSplashPool.obtain();
		pe.setPosition(l.leafSprite.getX() + l.leafSprite.getWidth() * 0.5f, l.leafSprite.getY() + l.leafSprite.getHeight() * 0.5f);
		pe.findEmitter("Untitled").getTint().setColors(l.leafDef.splatColor);
		_peSplashes.add(pe);
		
		//splats
		pe = _peSplatPool.obtain();		
		pe.findEmitter("Untitled").getTint().setColors(l.leafDef.splatColor);
		pe.findEmitter("Splat big").getTint().setColors(l.leafDef.splatColor);
		pe.setPosition(l.leafSprite.getX() + l.leafSprite.getWidth() * 0.5f, l.leafSprite.getY() + l.leafSprite.getHeight() * 0.5f);
		_peSplats.add(pe);
	}
	
	private void checkLeaf(Leaf l) {
		if(_activeCustomer != null && _activeCustomer.isWaitingForMed()) {
			Recipe r = _activeCustomer.getRecipe();
			if(r.pushLeaf((byte)l.id)) {
				//is recipe fulfilled?
				if(r.hasFulfilled()) {
					//shake the pot
					_pot.shakePot(1, false);
					
					//customer to go home
					_activeCustomer.payAndGoHome();
					_activeCustomer = null;

					//play kaching sound
					_kaching.play(GP.$().SOUND_VOL);
				} else {
					//add more happiness to that customer
					_activeCustomer.increaseHappiness();
				}
			} else {
				//shake the pot
				_pot.shakePot(1, true);
				
				//score minus
				_activeCustomer.showWrongLeafMinus();
				
				//play error sound
				_recipeFail.play(GP.$().SOUND_VOL);
			}
		}
	}
	
	private boolean isOutOfDisplay(Sprite s) {
		return s.getY() + s.getHeight() < 0;
	}

	@Override
	public void onCustomerPay(Customer c, int value) {
		_score = Math.max(0, _score + value);
	}
}
